#pragma once
#ifndef _KH_ENGINE_UNITS_H
#define _KH_ENGINE_UNITS_H
#include <Kharlia/Engine/Common.h>
#include <CEGUISize.h>
#include <CEGUIRect.h>

// Vector and Size classes are mutable in C++, but immutable in Python, much like other basic types exposed to python.

namespace kharlia { namespace engine {

template <typename T>
struct KH_ENGINE_API TVector2 {
    typedef TVector2<T> ThisType;
    typedef T DataType;

    DataType X, Y;

    TVector2(): X(), Y() {}
    TVector2(DataType v): X(v), Y(v) {}
    TVector2(DataType x, DataType y): X(x), Y(y) {}
    template <class N>
    TVector2(TVector2<N> const& other): X(DataType(other.X)), Y(DataType(other.Y)) {}

    ThisType operator+(ThisType const& other) const { return ThisType(X + other.X, Y + other.Y); }
    ThisType operator-(ThisType const& other) const { return ThisType(X - other.X, Y - other.Y); }
    ThisType operator*(ThisType const& other) const { return ThisType(X * other.X, Y * other.Y); }
    ThisType operator/(ThisType const& other) const { return ThisType(X / other.X, Y / other.Y); }

    ThisType const& operator+=(ThisType const& other) { X += other.X; Y += other.Y; return *this; }
    ThisType const& operator-=(ThisType const& other) { X -= other.X; Y -= other.Y; return *this; }
    ThisType const& operator*=(ThisType const& other) { X *= other.X; Y *= other.Y; return *this; }
    ThisType const& operator/=(ThisType const& other) { X /= other.X; Y /= other.Y; return *this; }
    
    ThisType operator+(DataType const& other) const { return ThisType(X + other, Y + other); }
    ThisType operator-(DataType const& other) const { return ThisType(X - other, Y - other); }
    ThisType operator*(DataType const& other) const { return ThisType(X * other, Y * other); }
    ThisType operator/(DataType const& other) const { return ThisType(X / other, Y / other); }
    
    ThisType const& operator+=(DataType const& other) { X += other; Y += other; return *this; }
    ThisType const& operator-=(DataType const& other) { X -= other; Y -= other; return *this; }
    ThisType const& operator*=(DataType const& other) { X *= other; Y *= other; return *this; }
    ThisType const& operator/=(DataType const& other) { X /= other; Y /= other; return *this; }

    bool operator==(ThisType const& other) const { return X == other.X && Y == other.Y; }
    bool operator!=(ThisType const& other) const { return !operator==(other); }

    ThisType& operator=(ThisType const& other) { X = other.X; Y = other.Y; return *this; }
    template <typename N>
    ThisType& operator=(TVector2<N> const& other) { X = DataType(other.X); Y = DataType(other.Y); return *this; }
    ThisType& operator=(DataType const& other) { X = other; Y = other; return *this; }

    DataType GetLength() const {
        return (DataType)sqrt((float)((X * X) + (Y * Y)));
    }

    bool IsZero() const { return (X == DataType(0) && Y == DataType(0)); }

    str PyRepr() const;
    str PyStr() const;

    static void InitClass();
};
typedef TVector2<float> FVector2F;
typedef TVector2<int> FVector2I;

template <typename T>
struct KH_ENGINE_API TSize {
    typedef TSize<T> ThisType;
    typedef T DataType;

    T Width, Height;

    TSize(): Width(), Height() {}
    TSize(T w, T h=0.0f): Width(w), Height(h) {}
    template <class N>
    TSize(TSize<N> const& other): Width(T(other.Width)), Height(T(other.Height)) {}
    TSize(ui::Size const& other): Width(T(other.d_width)), Height(T(other.d_height)) {}

    ThisType operator+(ThisType const& other) const { return ThisType(Width + other.Width, Height + other.Height); }
    ThisType operator-(ThisType const& other) const { return ThisType(Width - other.Width, Height - other.Height); }
    ThisType operator*(ThisType const& other) const { return ThisType(Width * other.Width, Height * other.Height); }
    ThisType operator/(ThisType const& other) const { return ThisType(Width / other.Width, Height / other.Height); }

    ThisType const& operator+=(ThisType const& other) { Width += other.Width; Height += other.Height; return *this; }
    ThisType const& operator-=(ThisType const& other) { Width -= other.Width; Height -= other.Height; return *this; }
    ThisType const& operator*=(ThisType const& other) { Width *= other.Width; Height *= other.Height; return *this; }
    ThisType const& operator/=(ThisType const& other) { Width /= other.Width; Height /= other.Height; return *this; }

    bool operator<(ThisType const& other) const { return (Width * Height) < (other.Width * other.Height); }
    bool operator==(ThisType const& other) const { return Width == other.Width && Height == other.Height; }
    bool operator!=(ThisType const& other) const { return !operator==(other); }

    ThisType& operator=(ThisType const& other) { Width = other.Width; Height = other.Height; return *this; }
    
    operator ui::Size() const { return ui::Size(float(Width), float(Height)); }

    T GetArea() const { return Width * Height; }

    static void InitClass();
};
typedef TSize<float> FSizeF;
typedef TSize<int> FSizeI;

template <typename T>
struct KH_ENGINE_API TRect {
    typedef TRect<T> ThisType;
    typedef T DataType;
    typedef TVector2<T> VectorType;
    typedef TSize<T> SizeType;

    TVector2<T> Pos;
    TSize<T> Size;

    TRect(T x, T y, T w, T h): Pos(x, y), Size(w, h) {}
    TRect(VectorType p = VectorType(), SizeType s = SizeType()): Pos(p), Size(s) {}
    template <class N>
    TRect(TRect<N> const& other): Pos(TVector2<T>(other.Pos)), Size(TSize<T>(other.Size)) {}
    TRect(ui::Rect const& other): Pos(other.d_left, other.d_top),
                                  Size(other.d_right - other.d_left, other.d_bottom - other.d_top) {}

    ThisType operator+(ThisType const& other) const { return ThisType(Pos + other.Pos, Size + other.Size); }
    ThisType operator-(ThisType const& other) const { return ThisType(Pos - other.Pos, Size - other.Size); }
    ThisType operator*(ThisType const& other) const { return ThisType(Pos * other.Pos, Size * other.Size); }
    ThisType operator/(ThisType const& other) const { return ThisType(Pos / other.Pos, Size / other.Size); }

    ThisType const& operator+=(ThisType const& other) { Pos += other.Pos; Size += other.Size; return *this; }
    ThisType const& operator-=(ThisType const& other) { Pos -= other.Pos; Size -= other.Size; return *this; }
    ThisType const& operator*=(ThisType const& other) { Pos *= other.Pos; Size *= other.Size; return *this; }
    ThisType const& operator/=(ThisType const& other) { Pos /= other.Pos; Size /= other.Size; return *this; }

    bool operator==(ThisType const& other) const { return Pos == other.Pos && Size == other.Size; }
    bool operator!=(ThisType const& other) const { return !operator==(other); }

    ThisType& operator=(ThisType const& other) { Pos = other.Pos; Size = other.Size; return *this; }

    operator ui::Rect() const { return ui::Rect(Pos.X, Pos.Y, Pos.X + Size.Width, Pos.Y + Size.Height); }

    T GetX() const { return Pos.X; }
    T GetY() const { return Pos.Y; }
    T GetWidth() const { return Size.Width; }
    T GetHeight() const { return Size.Height; }
    void SetX(T const& value) { Pos.X = value; }
    void SetY(T const& value) { Pos.Y = value; }
    void SetWidth(T const& value) { Size.Width = value; }
    void SetHeight(T const& value) { Size.Height = value; }

    T GetArea() const { return Size.GetArea(); }
    VectorType GetCenter() const { return VectorType(Pos.X + (Size.Width / 2), Pos.Y + (Size.Height / 2)); }
    T GetTop() const { return Pos.Y; }
    T GetLeft() const { return Pos.X; }
    T GetBottom() const { return Pos.Y + Size.Height; }
    T GetRight() const { return Pos.X + Size.Width; }

    str PyRepr() const;
    str PyStr() const;

    static void InitClass();
};

typedef TRect<float> FRectF;
typedef TRect<int> FRectI;

template <typename T>
struct KH_ENGINE_API TVector3 {
    typedef TVector3<T> ThisType;
    typedef T DataType;

    T X, Y, Z;

    TVector3(): X(), Y(), Z() {}
    TVector3(DataType v): X(v), Y(v), Z(v) {}
    TVector3(DataType x, DataType y, DataType z): X(x), Y(y), Z(z) {}
    template <class N>
    TVector3(TVector3<N> const& other): X(DataType(other.X)), Y(DataType(other.Y)), Z(DataType(other.Z)) {}

    ThisType operator+(ThisType const& other) const { return ThisType(X + other.X, Y + other.Y, Z + other.Z); }
    ThisType operator-(ThisType const& other) const { return ThisType(X - other.X, Y - other.Y, Z - other.Z); }
    ThisType operator*(ThisType const& other) const { return ThisType(X * other.X, Y * other.Y, Z * other.Z); }
    ThisType operator/(ThisType const& other) const { return ThisType(X / other.X, Y / other.Y, Z / other.Z); }

    ThisType const& operator+=(ThisType const& other) { X += other.X; Y += other.Y; Z += other.Z; return *this; }
    ThisType const& operator-=(ThisType const& other) { X -= other.X; Y -= other.Y; Z -= other.Z; return *this; }
    ThisType const& operator*=(ThisType const& other) { X *= other.X; Y *= other.Y; Z *= other.Z; return *this; }
    ThisType const& operator/=(ThisType const& other) { X /= other.X; Y /= other.Y; Z /= other.Z; return *this; }

    ThisType operator+(DataType const& other) const { return ThisType(X + other, Y + other, Z + other); }
    ThisType operator-(DataType const& other) const { return ThisType(X - other, Y - other, Z - other); }
    ThisType operator*(DataType const& other) const { return ThisType(X * other, Y * other, Z * other); }
    ThisType operator/(DataType const& other) const { return ThisType(X / other, Y / other, Z / other); }
    
    ThisType const& operator+=(DataType const& other) { X += other; Y += other; Z += other; return *this; }
    ThisType const& operator-=(DataType const& other) { X -= other; Y -= other; Z -= other; return *this; }
    ThisType const& operator*=(DataType const& other) { X *= other; Y *= other; Z *= other; return *this; }
    ThisType const& operator/=(DataType const& other) { X /= other; Y /= other; Z /= other; return *this; }

    bool operator==(ThisType const& other) const { return X == other.X && Y == other.Y && Z == other.Z; }
    bool operator!=(ThisType const& other) const { return !operator==(other); }

    ThisType& operator=(ThisType const& other) { X = other.X; Y = other.Y; Z = other.Z; return *this; }
    template <typename N>
    ThisType& operator=(TVector3<N> const& other) { X = DataType(other.X); Y = DataType(other.Y); Z = DataType(other.Z); return *this; }
    ThisType& operator=(DataType const& other) { X = other; Y = other; Z = other; return *this; }

    DataType GetLength() const {
        return (DataType)sqrt((float)((X * X) + (Y * Y) + (Z * Z)));
    }

    bool IsZero() const { return (X == DataType(0) && Y == DataType(0) && Z == DataType(0)); }
        
    str PyRepr() const;
    str PyStr() const;

    static void InitClass();
};

typedef TVector3<float> FVector3F;
typedef TVector3<int> FVector3I;

}}

#endif